﻿// YamlWriter.cs
//
// Copyright 2008 by Burt Harris
// Licensed under the Microsoft Public License (Ms-PL)
// See: http://www.codeplex.com/yaml/license
//

namespace YaTools.Yaml
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.Serialization;
    using YaTools.Yaml.Serialization;
    [Obsolete("Replace with Emitter/Mode/Waypoint")]
    public class YamlTextWriter : OldYamlWriter
    {

        internal const string DefaultLoalPrefix = "t:";

        internal enum LevelMode
        {
            Empty,
            Stream,
            Sequence, 
            Map, 
            String,
            Ended
        }

        ///////////////////////////////
        // YamlWriter Implementation //
        ///////////////////////////////

        public override void WriteLine()
        {
            _writer.WriteLine();
        }

        public override void WriteComment(string comment, bool indented)
        {
            if (indented)
                IndentLine();
            _writer.Write("# ");
            _writer.Write(comment);
        }

        public override void WriteScalar(object value, string tagUri)
        {
            PushLevel(_level.spaces + (_level.flow ? 0 : Settings.IndentSize), LevelMode.String, _level.flow);
            try
            {
                WriteTag(tagUri);
                if (_levels.Count == 0) _writer.WriteLine();

                /* For now, all ambiguous keys are going to be double-quoted */
                if ((_parent.mode == LevelMode.Map && _parent.nodeCount % 2 == 1) || _level.flow)
                {
                    Scalar.WriteDoubleQuoted(_writer, 0, value.ToString(), _level.spaces);
                    return;
                }

                Scalar.EmitScalar(this, Scalar.Style.Default, 0, Scalar.Chomping.Keep, value.ToString(), _level.flow, _level.spaces);
            }
            finally
            {
                PopLevel();
            }

        }

        public override void WriteItem(object value, Type impliedType)
        {
            switch (_level.mode)
            {
                case LevelMode.Ended:
                case LevelMode.Empty:
                case LevelMode.Stream:
                    WriteYamlDirective();

                    // Establish tags

                    Preprocess(value, impliedType);

                    foreach (var type in _surrogates.Keys)
                    {
                        var uri = _surrogates[type].GetUri(type);
                        var tag = TagFor(uri);
                        AddTag(uri, tag);
                    }

                    _writer.WriteLine();
                    _writer.Write("--- ");
                    WriteNode(value, impliedType);
                    _level.mode = LevelMode.Ended;
                    break;

                case LevelMode.Map:
                    WriteNode(value, impliedType);
                    break;

                case LevelMode.Sequence:
                    WriteSequencePrefix();
                    WriteNode(value, impliedType);
                    break;
            }
        }

        public override void WriteKey(object value, Type impliedType)
        {
            _level.writingKey = true;
            if (_level.flow)
            {
                if (_level.nodeCount > 0)
                {
                    _writer.Write(", ");
                }
            }
            // else
            {
                if (_parent.writingKey && _level.nodeCount == 0)
                {
                    _level.wasComplexKey = true;
                    _writer.Write('?');
                }

                /* map-in-seq shortcut */
                if (_parent.mode == LevelMode.Sequence
                    && _level.nodeCount == 0)
                {
                    ShortcutIndent();
                }
                else
                    IndentLine();
            }
            WriteNode(value, impliedType);
            _level.writingKey = false;
            _writer.Write(": ");
        }

        public override bool BeginList(object value, string tagUri, bool flowRequested)
        {
            // TODO: Check if we should generate an alias, and return false if so

            bool flow =_level.flow | flowRequested;     
            
            PushLevel( _level.spaces + Settings.IndentSize, LevelMode.Sequence, flow);

            if (WriteTag(tagUri) && !Settings.CompactNesting) IndentLine();

            if (flow) _writer.Write('[');
            
            return true;
        }

        public override void EndList()
        {
            if (_level.mode != LevelMode.Sequence)
                throw new InvalidOperationException("EndList called in state" + _level.mode.ToString());

            // Terminate the list

            bool wasComplexKey = _level.wasComplexKey;

            if (_level.flow)
                _writer.WriteLine(']');
            else if (_level.nodeCount == 0)
                _writer.WriteLine("[]");
            PopLevel();
            if (wasComplexKey)
                IndentLine();

        }

        public override bool BeginMap(object value, string tagUri, bool flowRequested)
        {
            flowRequested |= _level.flow;   // Inherit inline from parent level
            PushLevel(_level.spaces + Settings.IndentSize, LevelMode.Map, flowRequested);

            if (WriteTag(tagUri) && !Settings.CompactNesting) IndentLine();

            if (flowRequested)
                _writer.Write('{');

            return true;
        }

        public override void EndMap()
        {
            if (_level.mode != LevelMode.Map)
                throw new InvalidOperationException("EndMap called in state" + _level.mode.ToString());

            bool wasComplex = _level.wasComplexKey;
            if (_level.flow)
                _writer.Write('}');
            else if (_level.nodeCount == 0)
                _writer.Write("{}");
            else if (_level.nodeCount % 2 == 1)
                _writer.Write(": ");  // I think this should never happen
            PopLevel();
            if (wasComplex)
                IndentLine();
        }

  

        ////////////////////////////////
        // EndMode of YamlWriter  methods //
        ////////////////////////////////


        private void PushLevel(int indent, LevelMode status, bool inline)
        {
            _levels.Push(_parent);
            _parent = _level;
            _level = new Level(indent, status, inline);
        }

        private void PopLevel()
        {
            _level = _parent;
            _parent = _levels.Pop();
        }

        int _handleNumber = 0;

        public YamlWriterSettings Settings { get; set; }

        public enum TaggingOption { Never = 0, WhenNeeded, Always };

        protected class Level
        {
            internal LevelMode          mode;
            internal int                spaces;		        // Number of spaces of indent
            internal int                nodeCount;		    // Number of nodes emitted/parsed
            internal readonly bool      flow;
            internal bool               wasComplexKey = false;
            internal bool               writingKey = false;

            internal Level(int indent, LevelMode status, bool inline)
            {
                this.flow = inline;
                this.spaces = indent;
                this.mode = status;
            }
        }


        // Control of formatting

        public YamlWriterSettings YamlWriterSettings { get; set; }

        [CLSCompliant(false)]
        protected const UInt16 maxObjectCount = 100;

        private ISurrogateSelector _surrigateSelector = new YamlSurrogateSelector();

        /// <summary>
        /// Indicates if the formatter should apply anchors to 
        /// repeated string values.
        /// </summary>
        /// <remarks>
        /// Using anchors and references for repeated string values 
        /// can make the serialized data more compact, but tends to 
        /// reduce its readability.  It defaults to false.
        /// </remarks>
        public bool AnchorStrings = false;

        /// <summary>
        /// Indicates if the formatter should apply tags to all nodes.
        /// </summary>
        /// <remarks>
        /// This flag is primarily intended for testing purposes, where we 
        /// want the Serialization output format to be very explicit.  
        /// It defaults to false.
        /// </remarks>
        public bool ExplicitTyping = false;

        /// <summary>
        /// Flag indicating that a YAML Pause ("...") should be written at the end of each serialization
        /// </summary>
        public bool EmitPause = true;

        int _anchorNumber = 0;

        // Level stack 
        Level _parent;
        Level _level;
        Stack<Level> _levels;



        internal TextWriter _writer;

        Dictionary<Type, string> _predefinedTags;


        private void Indent(int count)
        {
            _writer.WriteSpaces(count);
        }

        public YamlTextWriter(TextWriter writer) : this( writer, StreamingContextStates.All )
        {
        }

        public YamlTextWriter(TextWriter writer, StreamingContextStates contextStates)
        {
            Utility.ValidateArgumentNotNull(writer, "writer");
            this.Settings = new YamlWriterSettings();

            _namespaces = new Dictionary<string, string>();
            _namespaces.Add(Tags.DefaultGlobalPrefix, Tags.GlobalHandle );
            _namespaces.Add(DefaultLoalPrefix, Tags.LocalHandle );

            this._writer = writer;
            this._level = this._parent = new Level(-Settings.IndentSize, LevelMode.Empty, false);
            this._levels = new Stack<Level>();
        }

        public ISurrogateSelector SurrogateSelector
        {
            get { return this._surrigateSelector; }
            //set
            //{
            //    if (value == null)
            //        throw new ArgumentNullException();
            //    this._surrigateSelector = value;
            //}
        }

        public StreamingContext Context
        {
            get { return this._streamingContext; }
            //set { this._streamingContext = value; }
        }

        private void WriteTagDirective(string handle, string uriPrefix)
        {
            Utility.ValidateArgumentNotNull(handle, "handle");
            Utility.ValidateArgumentNotNull(uriPrefix, "uriPrefix");

            if (handle.Length < 1 || handle[0] != '!' || handle[handle.Length-1] != '!')
                throw new ArgumentException( "handle must start and end with '!'", "handle" );

            switch (_level.mode)
            {
                case LevelMode.Empty:
                case LevelMode.Stream:
                case LevelMode.Ended:
                    break;
                default:
                    throw new InvalidOperationException("Too late to write directives");
            }

            WriteYamlDirective();   // It's smart enough to know if it should be done...

            _namespaces.Add(uriPrefix, handle);
        
            // Write out the tag directive

            _writer.WriteLine();
            _writer.Write("%TAG {0} {1}", handle, uriPrefix);
        }

        private string WriteTagDirective(string uriPrefix)
        {
            string tag = GenerateHandleFor(uriPrefix);
            WriteTagDirective(tag, uriPrefix);
            return tag;
        }

        private void WriteYamlDirective()
        {
            switch (_level.mode)
            {
                case LevelMode.Empty:
                    _writer.Write("%YAML 1.2");
                    _level.mode = LevelMode.Stream;
                    break;
                
                case LevelMode.Ended:
                    _writer.WriteLine();
                    _writer.WriteLine("...");
                    _level.mode = LevelMode.Stream; 
                    break;

                case LevelMode.Stream:
                    break;
            }
        }

        private void yamlType(string s, params Type[] types)
        {
            string tag = "tag:yaml.org,2002:" + s;
            foreach (Type t in types)
                _predefinedTags.Add(t, tag);
        }

        private void InitializeTags()
        {
            this._predefinedTags = new Dictionary<Type, string>();
            yamlType("str", Globals.TypeOfString);
            yamlType("bool", typeof(bool));
            yamlType("float", typeof(float), typeof(double));
            yamlType("int", typeof(Int16), typeof(Int32), typeof(Int64),
                            typeof(UInt16), typeof(UInt32), typeof(UInt64),
                            typeof(sbyte), typeof(byte));
            yamlType("binary", typeof(byte[]), typeof(sbyte[]));
            yamlType("timestamp", typeof(DateTime));
            yamlType("seq", typeof(Object[]));
            yamlType("map", typeof(Hashtable[]));
        }

        private static bool IsBreak(char c)
        {
            switch (c)
            {
                case '\r':
                case '\n':
                    return true;
            }
            return false;
        }

        void IndentLine()
        {
#if SHOW_FRAMES
            StackFrame caller = new StackFrame(1,true);
            _writer.Write( " # {0}", caller );
#endif
#if SHOW_LEVEL_NODES
            _writer.Write(" # indent: {1} nodes: {0}", _level.nodeCount, _level.spaces);
#endif
            _writer.WriteLine();
            Indent(_level.spaces);
        }


        /*
         * Start emitting from the given node, check for anchoring and then
         * issue the callback to the YamlFormatter handler.
         */
        void WriteNode(Object obj, Type impliedType)
        {
            _level.nodeCount++;

            // Deal with nulls up front

            if (obj == null) { _writer.Write("~"); return; }

            // Generate anchor and alias 

            string anchor;
            if (_anchors != null && _anchors.TryGetValue(obj, out anchor))
            {
                if (anchor != null)
                {
                    _writer.Write("*{0} ", anchor);
                    return;
                }
                else
                {
                    anchor = String.Format("A{0}", ++_anchorNumber);
                    _anchors[obj] = anchor;
                    _writer.Write("&{0} ", anchor);
                }
            }

            if (obj is string)
            {
                WriteScalar(obj, Tags.String);
            }
            else
            {
                Type varType = obj.GetType();
                switch (Type.GetTypeCode(varType))
                {
                    default:
                        //    // For now, the simple approach is what we want
                        //   _writer.Write(Convert.ToString(obj));
                        //    break;


                        //case TypeCode.Object:
                        YamlSurrogate surrogate;
                        if (!_surrogates.TryGetValue(varType, out surrogate))
                        {
                            throw new ArgumentException(String.Format("No Surrogate for type {0}", varType.FullName));
                        }
                        surrogate.GetObjectData(obj, null, _streamingContext);
                        break;
                }
            }

            if (_parent == null) /*( this.lvl_idx == 1 ) */
            {
               _writer.WriteLine();
            }
        }


        private void WriteSequencePrefix()
        {
            if (_level.flow && _level.nodeCount > 0)
            {
               _writer.Write(", ");
                return;
            }

            /* seq-in-map shortcut */
            if (_parent.mode == LevelMode.Map
                && _level.nodeCount == 0)
            {
                /* complex key */
                if (_parent.writingKey)
                {
                    _level.wasComplexKey = true;
                   _writer.Write('?');
                }
                else
                {
                    _level.spaces = _parent.spaces;
                }
            }

            /* seq-in-seq shortcut */
            else if (_parent.mode == LevelMode.Sequence
                && _level.nodeCount == 0)
            {
                int spcs = (_level.spaces - _parent.spaces) - 2;
                if (spcs >= 0)
                {
                    int i = 0;
                    for (i = 0; i < spcs; i++)
                    {
                       _writer.Write(' ');
                    }
                   _writer.Write("- ");
                    return;
                }
            }
            IndentLine();
           _writer.Write("- ");
        }
       
        private void ShortcutIndent()
        {
            int spaces = (_level.spaces - _parent.spaces) - 2;
            Indent(spaces);
        }

        private void WriteMapValue(Object obj, Type impliedType)
        {
           _writer.Write(": ");
            WriteNode(obj, impliedType);
        }

        private string TagFor(string uri)
        {
            string tag;
            if (_tags.TryGetValue(uri, out tag))
                return tag;


            string temp = uri.ToString();
            Utility.ValidateArgument(temp.Length > 1, "InvalidOperation URI Length", "uri");

            int i = temp.LastIndexOfAny(s_uriBreakCharacters);
            string prefix = temp.Substring(0, i + 1);
            string suffix = temp.Substring(i + 1);
            string handle;
            if (!_namespaces.TryGetValue(prefix, out handle))
            {
                handle = WriteTagDirective(prefix);
            }
            tag = String.Concat(handle, suffix, ' ');

            return tag;
        }

        private string GenerateHandleFor(string uriPrefix)
        {
            return String.Format("!H{0}!", ++_handleNumber);
        }


        private bool WriteTag(string uri)
        {
            if (uri != null)
            {
                string tag = TagFor(uri);
                if (tag != null)
                    _writer.Write(tag);
                else
                    _writer.Write("!<{0}> ", uri);
                return true;
            }
            return false;
        }

        private string DebugText { get { return _writer.ToString(); } }
    }
}
